import json
import re
import urllib.parse


class Span(object):
    def __init__(
            self,
            trace_id,
            sid,
            start_mus,
            duration_mus,
            op_name,
            references,
            process_id,
            span_kind,
            span_scope,
            span_tags
    ):
        self.trace_id = trace_id  # trace_id
        self.sid = sid  # span_id
        self.start_mus = start_mus  # 小数点后六位
        self.duration_mus = duration_mus  # 小数点后六位
        self.op_name = op_name  # span_name
        self.references = references  # span_link
        self.process_id = process_id  # 已经完成了 PID 到 Service（ContainerID）的映射。
        self.process = ''  # 惰性加载，parse_json_trace 之后可用。
        self.kind = span_kind  # 枚举 server/client。针对 checkout 要 fix 一下。
        self.scope = span_scope  # 作用域。
        self.tags = span_tags  # 标签。
        self.children_spans = []  # 内部维护的关联关系
        self.content = {}

    def ExtractContent(self):
        """Parsing 抽取各种形式的消息内容。
        # todo 引入优先级文法
        """
        self.content.update(self._extract_content_from_op_name())

        self.content.update(self._extract_content_from_url())

        self.content.update(self._extract_content_from_db_statement())

        self.content.update(self._extract_content_from_app_tag())

        self.content.update(self._extract_content_from_content_tag())

    def _extract_content_from_op_name(self):
        """从 GET Path 中提取消息内容"""
        # 1. HTTP GET 操作
        if self.op_name.startswith('GET ') and '{' in self.op_name:
            # 提取路径中的参数名，例如从 "/api/products/{productId}" 中提取 "productId"
            path_pattern = self.op_name[4:]  # 移除 "GET " 前缀
            # 查找形如 {paramName} 的参数
            params = re.findall(r'\{([^}]+)\}', path_pattern)
            if params:
                # 在 http.target 中查找实际值
                http_target = None
                for tag in self.tags:
                    if tag.get('key') == 'http.target':
                        http_target = tag.get('value', '')
                        break

                extracted_content = {}
                if http_target:
                    # 解析 http.target 中的实际值
                    target_path = urllib.parse.urlparse(http_target).path
                    pattern_parts = path_pattern.split('/')
                    target_parts = target_path.split('/')

                    # 匹配路径段并提取参数值
                    for i, part in enumerate(pattern_parts):
                        if part.startswith('{') and part.endswith('}'):
                            param_name = part[1:-1]  # 去掉大括号
                            if i < len(target_parts):
                                extracted_content[param_name] = target_parts[i]
                else:
                    # 如果没有找到 http.target，则只保留参数名
                    for param in params:
                        extracted_content[param] = None
                return extracted_content

        return {}

    def _extract_content_from_url(self):
        """从 GET 参数列表中提取消息内容"""

        # 查找 http.target 或 http.url 标签
        http_target = None
        for tag in self.tags:
            if tag.get('key') in ['http.target', 'http.url']:
                http_target = tag.get('value', '')
                break
        if not http_target:
            return {}

        # 解析 URL 并提取查询参数，也就是问号之后的参数列表
        parsed_url = urllib.parse.urlparse(http_target)
        query_params = urllib.parse.parse_qs(parsed_url.query)

        # 将参数值从列表转换为单个值（取第一个）
        extracted_params = {}
        for key, values in query_params.items():
            extracted_params[key] = values[0] if values else ''

        return extracted_params

    def _extract_content_from_db_statement(self):
        """从 DB 语句中提取消息内容"""
        db_system = None
        db_statement = None
        for tag in self.tags:
            if tag.get('key') == 'db.system':
                db_system = tag.get('value', '')
            elif tag.get('key') == 'db.statement':
                db_statement = tag.get('value', '')
        if not db_system or not db_statement:
            return {}

        # 1. Redis 系统。特定于 otel-demo，非通用格式。
        if db_system == 'redis':
            redis_cmd, redis_key = db_statement.split(' ')
            if redis_cmd in ['HGET', 'HMSET', 'EXPIRE']:
                return {
                    'unknown': redis_key
                }

        # 2. MySQL 系统
        # fixme 先要插桩 cart userId

        return {}

    def _extract_content_from_app_tag(self):
        """从 tags 中提取消息内容并反序列化为字典

        支持的 tag:
        - app.*

        """
        app_content = {}
        for tag in self.tags:
            if tag.get('key').startswith('app.'):
                key = tag.get('key').replace('.', '_')  # 点转下划线
                app_content[key] = tag.get('value')
        return app_content

    def _extract_content_from_content_tag(self):
        """从 tags 中提取消息内容并反序列化为字典
        
        支持的 tag:
        - rpc.grpc.content
        - http.request_content
        - messaging.kafka.message.value
        - message.outbound.body

        """

        content_keys = [
            'rpc.grpc.content',
            'http.request_content',
            'messaging.kafka.message.value',
            'message.outbound.body'
        ]

        for tag in self.tags:
            if tag.get('key') in content_keys:
                content_value = tag.get('value', '')
                if content_value:
                    try:
                        # 尝试将 JSON 字符串反序列化为字典
                        return json.loads(content_value)
                    except json.JSONDecodeError:
                        # 如果反序列化失败，返回原始字符串包装在字典中
                        return {'raw_content': content_value}

        # 如果没有找到任何内容字段，返回空字典
        return {}

    def AddChild(self, child_span_id):
        self.children_spans.append(child_span_id)

    def GetProcessId(self):
        return self.process_id

    def GetProcess(self, all_processes):
        """或者直接使用 span.process，两者等价。"""
        return all_processes[self.trace_id][self.process_id]

    def GetChildProcess(self, all_processes, all_spans):
        assert self.kind == "client"

        if len(self.children_spans) == 0:  # edge, leaf
            return "server_" + self.op_name

        assert len(self.children_spans) == 1
        return all_processes[self.trace_id][
            all_spans[self.children_spans[0]].process_id
        ]

    def GetParentProcess(self, all_processes, all_spans):
        # 针对 Root Span，返回的是自身信息。
        if self.IsRoot():  # edge, root
            return "client_" + self.op_name

        assert len(self.references) == 1
        # 采用 "CHILD_OF" 的规范格式。
        # assert self.references['refType'] == "CHILD_OF"
        parent_span_id = self.references[0]
        return all_processes[self.trace_id][all_spans[parent_span_id].process_id]

    def GetId(self):
        return (self.trace_id, self.sid)

    def IsRoot(self):
        return len(self.references) == 0

    def IsInSpan(self, process=None):
        """
        Server Span 作为 in_span 的判别较为简单。
        针对 frontend 洋葱模型，需要判断 scope。
        """
        if process is None:
            process = self.process

        def in_of_frontend():
            return self.kind == "server" and self.scope == "next.js"

        def in_of_generic_service():
            return self.kind == "server"

        if "frontend" == process:
            return in_of_frontend()
        else:
            return in_of_generic_service()

    def IsOutSpan(self, process=None):
        """
        out_span 利用规则匹配进行判别。
        针对 checkout 特别处理，是由采集决定的：kind 为 internal 实际上为 Client Span。
        """
        if process is None:
            process = self.process

        def out_of_checkout():
            if self.kind == "producer":  # kafka as downstream
                return True
            return self.kind == "internal"

        def out_of_frontend():
            scope_is_coll = self.kind == "client" and self.scope == "frontend-message-collector"
            return scope_is_coll
            # op_is_convert = 'convert' in self.op_name.lower()
            # return scope_is_coll and not op_is_convert

        def out_of_cart():
            return self.kind == "client" and self.op_name in ['HGET', 'HMSET', 'EXPIRE']

        def out_of_shipping():
            return self.kind == "client" and self.op_name.startswith("shipping.")

        def out_of_generic_service():
            return self.kind == "client"

        if "checkout" == process:
            return out_of_checkout()
        elif "frontend" == process:
            return out_of_frontend()
        elif "cart" == process:
            return out_of_cart()
        elif "shipping" == process:
            return out_of_shipping()
        else:
            return out_of_generic_service()

    def __lt__(self, other):
        return self.start_mus < other.start_mus

    def happens_before(self, other):
        # 如果是异步调用
        if self.kind == "producer" and other.kind == "consumer":
            return self.start_mus < other.start_mus
        # 如果是同步调用
        return (self.start_mus < other.start_mus and
                self.start_mus + self.duration_mus > other.start_mus + other.duration_mus)

    def __repr__(self):
        if self.start_mus == "None":
            return "Span:(%s, %s, %s, %s, %s, %s)" % (
                self.trace_id,
                self.sid,
                self.op_name,
                self.start_mus,
                self.duration_mus,
                self.kind,
            )
        else:
            return "Span:(%s, %s, %s, %d, %d, %s)" % (
                self.trace_id,
                self.sid,
                self.op_name,
                self.start_mus,
                self.duration_mus,
                self.kind,
            )

    def __str__(self):
        return self.__repr__()

    # 基于边的 Endpoint 定义
    def GetOperation(self):
        # op 命名模式: 存在 space、.、/、_
        return self.op_name

    # 因为 http method 存在重复，所以使用 {Process, Operation} 唯一标识 Operation。
    def GetProcessOperation(self, all_processes):
        return self.GetProcess(all_processes) + "," + self.GetOperation()

    # 基于端点的 Endpoint 定义
    def GetParentProcessOperation(self, all_processes, all_spans):
        # assert self.span_kind == "server"  # 不需要
        if len(self.references) == 0:
            return "client_" + self.GetProcess(all_processes) + "," + self.op_name
        assert len(self.references) == 1
        parent_span = all_spans[self.references[0]]
        parent_span_process_operation = parent_span.GetProcessOperation(all_processes)
        return parent_span_process_operation

    # 基于端点的 Endpoint 定义
    def GetChildProcessOperation(self, all_processes, all_spans):
        assert self.kind == "client"
        if len(self.children_spans) == 0:
            return "server_" + self.GetProcess(all_processes) + "," + self.op_name
        assert len(self.children_spans) == 1
        child_span = all_spans[self.children_spans[0]]
        child_span_process_operation = child_span.GetProcessOperation(all_processes)
        return child_span_process_operation

    # 约定 Endpoint 定义
    def GetParentEndpoint(self, all_processes, all_spans):
        # return self.GetParentProcessOperation(all_processes, all_spans)
        return self.GetProcessOperation(all_processes)

    def GetChildEndpoint(self, all_processes, all_spans):
        # return self.GetChildProcessOperation(all_processes, all_spans)
        return self.GetProcessOperation(all_processes)
